//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-10-30     ZhangYihua   first version
//
// Description  : DDS
//################################################################################
//  
//            2^(-kp)  cfg_freq_word
//             |            |       /---<-----\
//    in_err   V   pinc_dat V       |         |   out_phs
// ---------->(*)-(st)-[D]-(+)---->(+)-->[D]----(t)--------->
//      |     ______        ^           phs_nco    
//      ---->|      |       |
//           |u_iacc|-------
// 2^(-ki)-->|______|  iinc_dat(ofst_rtv)
//               ^
//               |
//            ofst_ini
//
module pi_nco #(    // proportional and integral NCO
parameter           IN_DW                   = 10,
parameter           IN_FW                   = 9,
parameter           PHS_FW                  = 9,    // must PHS_FW<=NCO_FW
parameter           NCO_FW                  = 24,   // must NCO_FW<=IN_FW+KI_MAX
parameter           FREQ_DW                 = 16,   // FREQ_DW<=NCO_FW or FREQ_DW>NCO_FW is both OK
parameter           PINC_DW                 = 12,   // must PINC_DW<FREQ_DW, and PINC_DW<=NCO_FW+IN_DW-IN_FW-KP_MIN
parameter           IINC_DW                 = 10,   // must IINC_DW<FREQ_DW, IACC is for freqency offset, it's small
parameter           IACC_FW                 = 24,   // must NCO_FW<=IACC_FW<=IN_FW+KI_MAX
parameter           KP_MAX                  = 15,
parameter           KP_MIN                  = 8,
parameter           KI_MAX                  = 23,   // generally KI_MAX>=KP_MAX
parameter           KI_MIN                  = 12,

// the following parameters are calculated automatically
parameter           OVER_BW                 = (FREQ_DW<=NCO_FW) ? 1 : 1+FREQ_DW-NCO_FW,
parameter           KPW                     = $clog2(KP_MAX+1), // range [KP_MIN:KP_MAX]
parameter           KIW                     = $clog2(KI_MAX+1)  // range [KI_MIN:KI_MAX]
) ( 
input                                       rst_n,
input                                       clk,
input                                       cke,        // cke for NCO and ACC

input                                       in_vld,     // refer to cke
input       signed  [IN_DW-1:0]             in_err,     // s(  IN_DW,   IN_FW)
output              [PHS_FW-1:0]            out_phs,    // u( PHS_FW,  PHS_FW), only fraction bits
output              [OVER_BW-1:0]           out_over_1p,   // NCO over, only integer bits

input               [KPW-1:0]               cfg_kp,     // range [KP_MIN:KP_MAX], gain = 2^(-kp)
input               [KIW-1:0]               cfg_ki,     // range [KI_MIN:KI_MAX], gain = 2^(-ki)

input               [FREQ_DW-1:0]           cfg_freq_word,  // u(FREQ_DW, NCO_FW),  output frequency is F_clk*freq_word/(2^NCO_FW)

input                                       cfg_ofst_clr,
input                                       cfg_ofst_frz,
input       signed  [IINC_DW-1:0]           cfg_ofst_ini,   // s(IINC_DW, IINC_FW), initial value
output      signed  [IINC_DW-1:0]           sts_ofst_rtv    // s(IINC_DW, IINC_FW), real-time value
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          INC_DW                  = MAX_F(IINC_DW, PINC_DW)+1;

wire        signed  [INC_DW-1:0]            inc_dat;
wire                [FREQ_DW+1-1:0]         freq_word_c;
wire                [FREQ_DW-1:0]           freq_word;

//################################################################################
// main
//################################################################################

// out_dat = in_dat*2^(-kp) + iacc; iacc = iacc + in_dat*2^(-ki);
pi_ctrl #(    // proportional and integral accumulator 
        .IN_DW                          (IN_DW                          ),
        .IN_FW                          (IN_FW                          ),
        .OUT_FW                         (NCO_FW                         ),	// must OUT_FW<=IN_FW+KI_MAX
        .PINC_DW                        (PINC_DW                        ),	// must PINC_DW<=OUT_FW+IN_DW-IN_FW-KP_MIN
        .IINC_DW                        (IINC_DW                        ),
        .IACC_FW                        (IACC_FW                        ),	// must OUT_FW<=IACC_FW<=IN_FW+KI_MAX
        .KP_MAX                         (KP_MAX                         ),
        .KP_MIN                         (KP_MIN                         ),
        .KI_MAX                         (KI_MAX                         ),	// must KI_MAX>=KP_MAX
        .KI_MIN                         (KI_MIN                         )
) u_pi_ctrl ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .in_vld                         (in_vld                         ),
        .in_dat                         (in_err                         ),
        .out_vld                        (                               ),
        .out_dat                        (inc_dat                        ),

        .cfg_kp                         (cfg_kp                         ),	// range [KP_MIN:KP_MAX], gain 
        .cfg_ki                         (cfg_ki                         ),	// range [KI_MIN:KI_MAX], gain 

        .cfg_acc_clr                    (cfg_ofst_clr                   ),
        .cfg_acc_frz                    (cfg_ofst_frz                   ),
        .cfg_acc_ini                    (cfg_ofst_ini                   ),	// s(IINC_DW, IINC_FW), initial value
        .sts_acc_rtv                    (sts_ofst_rtv                   )	// s(IINC_DW, IINC_FW), real-time value
);

assign freq_word_c = {1'b0, cfg_freq_word} + {{FREQ_DW+1-INC_DW{inc_dat[INC_DW-1]}}, inc_dat};

u_sat_tru_reg #(     // range [-(2^(IDW-1))/(2^IFW):(2^(IDW-1)-1)/(2^IFW)]
        .IDW                            (FREQ_DW+1                      ),	// input data width
        .IFW                            (NCO_FW                         ),	// input fractional width
        .ODW                            (FREQ_DW                        ),	// output data width
        .OFW                            (NCO_FW                         )	// output fractional width
) u_freq_word_tru ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .id                             (freq_word_c                    ),	// s(IDW, IFW), the MSB is sign
        .od                             (freq_word                      ),	// s(ODW, OFW), the MSB is sign
        .over                           (                               )
);

nco #(
        .PHS_FW                         (PHS_FW                         ),
        .NCO_FW                         (NCO_FW                         ),
        .FREQ_DW                        (FREQ_DW                        ),	// FREQ_DW<=NCO_FW or FREQ_DW>NCO_FW is both OK
        .RND_EN                         (1'b0                           ) 	// 1'b0:truncate lower bits directly; 1'b1:rounding
) u_nco ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .freq_word                      (freq_word                      ),	// output frequency is F_clk*freq_word/(2^NCO_FW)

        .over_1p                        (out_over_1p                    ),	// combinational logic output
        .phase                          (out_phs                        )
);

`include "func_param.v" // refer to MAX_F()

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off

initial begin
    if (PHS_FW>NCO_FW) begin
        $error("PHS_FW>NCO_FW.");
        $stop;
    end

    if (NCO_FW>IN_FW+KI_MAX) begin
        $error("NCO_FW is wrong.");
        $stop;
    end

    if ((PINC_DW>=FREQ_DW) || (PINC_DW>NCO_FW+IN_DW-IN_FW-KP_MIN)) begin
        $error("PINC_DW width is bigger.");
        $stop;
    end

    if (IINC_DW>=FREQ_DW) begin
        $error("IINC_DW width is bigger than FREQ_DW.");
        $stop;
    end

    if ((NCO_FW>IACC_FW) || (IACC_FW>IN_FW+KI_MAX)) begin
        $error("IACC_CW is wrong.");
        $stop;
    end
end

// synopsys translate_on
`endif

endmodule
